package com.yuandian.business.monitor.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yuandian.api.business_monitor.entity.BusinessDetail;
import com.yuandian.api.business_monitor.entity.BusinessResource;
import com.yuandian.api.business_monitor.entity.SimpleSegmentRelationship;
import com.yuandian.api.business_monitor.po.AlarmParams;
import com.yuandian.api.business_monitor.po.AlarmReverseAnalysisParams;
import com.yuandian.api.business_monitor.po.QueryBusinessStepParams;
import com.yuandian.api.business_monitor.vo.*;
import com.yuandian.api.management.entity.ConfBusiness;
import com.yuandian.api.management.entity.ConfNodeProperty;
import com.yuandian.api.management.entity.ConfStep;
import com.yuandian.api.management.enums.GraphType;
import com.yuandian.api.management.feign.*;
import com.yuandian.bpm.common.core.util.R;
import com.yuandian.bpm.common.data.resolver.ParamResolver;
import com.yuandian.business.monitor.mapper.*;
import com.yuandian.business.monitor.service.IBusiDetailStatService;
import com.yuandian.constant.ConfigKey;
import com.yuandian.constant.Constants;
import com.yuandian.utils.DataTypeUtil;
import com.yuandian.utils.PageDataInfo;
import com.yuandian.utils.StringUtils;
import com.yuandian.utils.YDateUtil;
import com.yuandian.utils.poi.ExcelTitleUtils;
import com.yuandian.utils.poi.ExcelUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 详单分钟/小时统计表 服务实现类
 * </p>
 *
 * @author fucx
 * @since 2020-11-10
 */
@Slf4j
@Service
public class BusiDetailStatServiceImpl extends ServiceImpl<BusiDetailStatMapper, BusiDetailStatAlarmVo>
		implements IBusiDetailStatService {

	/**
	 * 是前端关联指标
	 */
	private static final String ISFRONTCONNECTFLAG = "1";

	/**
	 * 是后端关联指标
	 */
	private static final String ISBACKCONNECTFLAG = "2";

	/**
	 * 是前后端关联指标
	 */
	private static final String ISCONNECTFLAG = "3";

	@Autowired
	private BusiDetailStatMapper busiDetailStatMapper;

	@Autowired
	private BusiAlarmMapper busiAlarmMapper;
	@Autowired
	private RemoteSystemService remoteSystemService;

	@Autowired
	private BusiAdvancedAlarmMapper busiAdvancedAlarmMapper;
	@Autowired
	private RemoteSegmentService remoteSegmentService;
	@Autowired
	private RemoteStepService remoteStepService;
	@Autowired
	private RemoteBusinessService businessService;


	private static final String ALARM_TABLE_NAME = "dwd_business_alarm";
	@Autowired
	private BaseAlarmMapper baseAlarmMapper;

	@Autowired
	private BusinessDetailMapper businessDetailMapper;

	@Autowired
	private StepDetailMapper stepDetailMapper;
	@Autowired
	private RemoteAlarmIndicatorsService remoteAlarmIndicatorsService;
	@Autowired
	private RemoteItRoadMapService remoteItRoadMapService;
	@Autowired
	private RemoteBusinessService remoteBusinessService;
	@Autowired
	private ExclusiveIndicator exclusiveIndicator;


	/**
	 * 端到端关联反向映射
	 */
	public static Map<String, List<SimpleSegmentRelationship>> ssrReverseMap = new HashMap<>();


	/**
	 * 查询告警业务信息
	 *
	 * @param params 告警参数
	 * @return 返回分页对象
	 */
	@Override
	public R getBusinessAlarm(AlarmParams params) {
		// 1、获取表信息集
//        List<MultipleTableInfo> multipleTableInfoList = DynamicTableNameUtil.generalMultipleTable(
//                BaseTableName.MYSQL_BUSI_DETAIL_ALARM_STAT.getName(),
//                params.getStartTime(),
//                params.getEndTime());
		// 2、校验表是否存在，剔除不存在的表
//        multipleTableInfoList = checkTableExistService.checkMysqlTableExist(multipleTableInfoList);
//        if (CollectionUtils.isEmpty(multipleTableInfoList)) {
//            return R.failed(
//                    ApiResultCode.NO_EXIST_TABLE_DYNAMIC.getCode(),
//                    ApiResultCode.NO_EXIST_TABLE_DYNAMIC.getMsg());
//        }
		// 3、设置表信息集
//        params.setMultipleTableInfoList(multipleTableInfoList);
		// 4、设置分页对象，如果不传pageNum或pageSize，相当于不分页
		Page<BusiDetailStatAlarmVo> page = null;
		if (params.getPageNum() > 0) {
			page = new Page<>(params.getPageNum(), params.getPageSize());
		}
		//设置告警类型集合
		if (params != null && (params.getAlarmTypeIds() == null || params.getAlarmTypeIds().size() < 1)) {
			List<Integer> typeList = new ArrayList<Integer>();
			typeList.add(0);
			params.setAlarmTypeIds(typeList);
		}
		// 去掉前后空格
		if (params != null && StringUtils.isNotBlank(params.getBusinessName())) {
			params.setBusinessName(params.getBusinessName().trim());
		}
		//5.查询数据
		List<BusiDetailStatAlarmVo> list = busiDetailStatMapper.queryBusiDetail(page, params);
		// 6、返回分页对象
		if (page == null) {
			return R.ok(new PageDataInfo(list.size(), list));
		}
		return R.ok();
	}

	@Override
	public void exportExcel(AlarmParams params, HttpServletResponse response) throws IOException {

		// 4、设置分页对象，如果不传pageNum或pageSize，相当于不分页
		Page<BusiDetailStatAlarmVo> page = null;
		if (params.getPageNum() > 0) {
			page = new Page<>(params.getPageNum(), params.getPageSize());
		}
		//设置告警类型集合
		if (params != null && (params.getAlarmTypeIds() == null || params.getAlarmTypeIds().size() < 1)) {
			List<Integer> typeList = new ArrayList<Integer>();
			typeList.add(0);
			params.setAlarmTypeIds(typeList);
		}
		// 去掉前后空格
		if (params != null && StringUtils.isNotBlank(params.getBusinessName())) {
			params.setBusinessName(params.getBusinessName().trim());
		}
		List<BusiDetailExcelVo> list = new ArrayList<>();
		//查询数据
		list = busiDetailStatMapper.queryBusiDetailExcelVo(params);
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
		String folder = Constants.EXPORT_PATH + File.separator + sdf.format(date) + File.separator;

		String fileName = "busiDetailAlarm_" + System.currentTimeMillis() + ".xlsx";
		String filePath = folder + fileName;
		int number = 1;
		for (BusiDetailExcelVo busiDetailExcelVo : list) {
			busiDetailExcelVo.setNumber(number++);
		}
		List<List<String>> headList = new ArrayList<>();
		List<String> titleList = Arrays.asList("序号", "业务名称", "告警类型", "访问量", "完整访问量", "平均办理时长", "失败访问量", "成功率");
		String title = "告警业务列表";
		List<String> titleParams = new ArrayList<>();
		titleParams.add(title);
		String name = remoteSystemService.getSystemById(params.getSystemId()).getData().getName();
		titleParams.add(name);
		//当告警类型有且只有一条时才展示
		if (params.getAlarmTypeIds() != null && params.getAlarmTypeIds().size() == 1) {
			titleParams.add(remoteAlarmIndicatorsService.queryById(params.getAlarmTypeId()).getData().getName());
		}
		String formTitleName = ExcelTitleUtils.formTitleName(titleParams, params.getStartTime(), params.getEndTime());
		titleList.forEach(s -> {
			List<String> childHeadList = new ArrayList<>();
			childHeadList.add(formTitleName);
			childHeadList.add(s);
			headList.add(childHeadList);
		});

		ExcelUtil.writeExcel(BusiDetailExcelVo.class, filePath, title, list, headList, response);


	}

	/**
	 * 查询业务告警信息(后面改成查询基础告警)
	 *
	 * @param params 告警参数
	 * @return 返回分页对象
	 */
	@Override
	public R getBusinessList(AlarmParams params) {
		if (params.getOrderByColumn() == null || ("").equals(params.getOrderByColumn())) {
			// 设置排序，默认按时间降序
			params.setOrderByColumn("alarmTriggerTime");
			params.setIsAsc("desc");
		}

		if (params.getPageNum() == 0) {
			params.setPageNum(1);
		}
		if (params.getPageSize() == 0) {
			params.setPageSize(10);
		}
		String category = params.getCategory();
		boolean isApm = false;
		if (StringUtils.isNotEmpty(category)) {
			isApm = "APM".equals(category.trim().toUpperCase());
		}
		Page page = new Page<>(params.getPageNum(), params.getPageSize());
		List<Map<String, Object>> list = new ArrayList<>();
		//查询数据
		if (isApm) {
			list = baseAlarmMapper.selectAPMBaseAlarm(page, params);
		} else {
			list = baseAlarmMapper.selectBaseAlarm(page, params);
		}
		if (list != null && list.size() > 0) {
			LinkedHashMap<String, Object> customMap = exclusiveIndicator.getCustomMap();
			for (Map<String, Object> map : list) {
				map.put("requestIpPort", (map.get("requestIp") + ":" + map.get("requestPort")));
				map.put("responseIpPort", (map.get("responseIp") + ":" + map.get("responsePort")));
				// 对专属指标进行切割
				ExclusiveIndicator.getValueFromSpecifyParameters(map, customMap);
			}
		}
		return R.ok(new PageDataInfo(page, list));
	}

	/**
	 * 业务步骤导出查询告警列表
	 *
	 * @param params 告警参数
	 * @return 返回分页对象
	 */
	@Override
	public R getBusinessListForStepExport(QueryBusinessStepParams params) {

		Page page = new Page<>(params.getPageNum(), params.getPageSize());
		//查询数据
		List<Map<String, Object>> list = baseAlarmMapper.selectBaseAlarmForStepExport(page, params);
		if (list != null && list.size() > 0) {
			LinkedHashMap<String, Object> customMap = exclusiveIndicator.getCustomMapInner();
			for (Map<String, Object> map : list) {
				map.put("requestIpPort", (map.get("requestIp") + ":" + map.get("requestPort")));
				map.put("responseIpPort", (map.get("responseIp") + ":" + map.get("responsePort")));
				// 对专属指标进行切割
				ExclusiveIndicator.getValueFromSpecifyParameters(map, customMap);
			}
		}
		return R.ok(new PageDataInfo(page, list));
	}

	/**
	 * 初始化端到端关联反向映射
	 */
	private void initSsrReverseMap() {

		try {
			// 获取端对端所有的关系
			List<SingleResourceItRelationship> srirList = queryList(null);

			if (CollectionUtils.isEmpty(srirList)) {
				return;
			}
			List<SimpleSegmentRelationship> list = null;
			List<String> codes = new ArrayList<String>();
			for (SingleResourceItRelationship srir : srirList) {
				String beforeCoding = srir.getBeforeBusinessResourceCoding();
				String afterCoding = srir.getAfterBusinessResourceCoding();
				Integer beforeSegmentId = srir.getBeforeSegmentId();
				Integer afterSegmentId = srir.getAfterSegmentId();
				// 去重
				String code = beforeCoding + beforeSegmentId + afterCoding + afterSegmentId;
				if (codes.contains(code)) {
					continue;
				}
				codes.add(code);
				// 忽略前后端一样的数据
				if (beforeCoding.equals(afterCoding) && (Objects.equals(beforeSegmentId, afterSegmentId))) {
					continue;
				}
				String key = afterCoding + "_" + afterSegmentId;
				SimpleSegmentRelationship simpleSegmentRelationship = new SimpleSegmentRelationship();
				simpleSegmentRelationship.setKey(key);
				BusinessResource beforeResource = new BusinessResource();// todo BpmStartConfig.BusinessResourcesMap.get(beforeCoding);
				BusinessResource afterResource = new BusinessResource();// todo BpmStartConfig.BusinessResourcesMap.get(afterCoding);

				if (beforeResource != null) {
					// 如果不为空，则对simpleSegmentRelationship对象的before属性进行赋值
					simpleSegmentRelationship.setBeforeBusinessResourceCoding(beforeCoding);
					simpleSegmentRelationship.setBeforeType(beforeResource.getType());
					simpleSegmentRelationship.setBeforeCategory(beforeResource.getCategory());
					simpleSegmentRelationship.setBeforeSegmentId(srir.getBeforeSegmentId());
				}
				if (afterResource != null) {
					// 如果不为空，则对simpleSegmentRelationship对象的after属性进行赋值
					simpleSegmentRelationship.setAfterBusinessResourceCoding(afterCoding);
					simpleSegmentRelationship.setAfterType(afterResource.getType());
					simpleSegmentRelationship.setAfterCategory(afterResource.getCategory());
					simpleSegmentRelationship.setAfterSegmentId(srir.getAfterSegmentId());
				}

				if (ssrReverseMap.containsKey(key)) { // 已包含
					list = ssrReverseMap.get(key);
					list.add(simpleSegmentRelationship);
				} else {
					list = new ArrayList<SimpleSegmentRelationship>();
					if (ObjectUtil.isNotEmpty(simpleSegmentRelationship.getAfterBusinessResourceCoding())
							&& !"null".equals(simpleSegmentRelationship.getAfterBusinessResourceCoding())) {
						list.add(simpleSegmentRelationship);
						ssrReverseMap.put(key, list);
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private List<SingleResourceItRelationship> queryList(SingleResourceItRelationship srir) {
		List<SingleResourceItRelationship> list = null;
		try {
			if (srir == null) {
				srir = new SingleResourceItRelationship();
				srir.setMatching(-1);
				srir.setFindTotal(-1);
				srir.setMatchAmount(-1);
				srir.setIsStatus(-1);
			}
			if (srir != null && srir.getPage() > 0) {
				srir.setPage((srir.getPage() - 1) * srir.getPagesize());
			}
//			list = busiAlarmMapper.querySingleResourceItRelationship(srir);
			if (list != null && list.size() > 0) {
				if (srir.getPagesize() > 0) {
					double count = list.get(0).getCount();
					list.get(0).setTotalPage((int) Math.ceil(count / srir.getPagesize()));
				}
			}
		} catch (Exception e) {
			log.error("系统异常!", e);
			e.printStackTrace();
			throw e;
		}
		return list;
	}


	/**
	 * 告警反推分析
	 *
	 * @param params 告警反推分析请求参数
	 * @return 告警反推分析结果
	 * @throws Exception
	 */
	@Override
	public Map<String, Object> reverseAnalysis(AlarmReverseAnalysisParams params) throws Exception {
		long startTime = System.currentTimeMillis();
		if (CollectionUtils.isEmpty(ssrReverseMap)) {
			initSsrReverseMap();
			log.info("告警反推-初始化端到端关联反向映射,耗时: {} MS", System.currentTimeMillis() - startTime);
		}

		// 返回的结果集
		Map<String, Object> map = new HashMap<>();
		List<AlarmReverseAnalysisVo> res = new ArrayList<>();
		List<PossibleBusinessDetailVo> pbdVoList = new ArrayList<>();
		map.put("alarmReverseAnalysis", res);
		map.put("possibleBusinessDetail", pbdVoList);

		// 1、根据告警数据id查询告警信息(非首端)
		BasalAlarm alarm = queryAlarmDataById(params);
		if (alarm == null) {
			log.info("告警数据为空");
			return map;
		}

		// 2、往前推寻找首端
		//2.1 查询所有端数据
		ConfNodeProperty confNodeProperty = new ConfNodeProperty();
		confNodeProperty.setType(GraphType.END_AND_END_RELATIONSHIP.getType());
		confNodeProperty.setSystemId(alarm.getSystemId());
		confNodeProperty.setGraphId(alarm.getSystemId().toString());
		List<Map<String, Object>> nodeList = remoteItRoadMapService.segmentMonitorDetailItRoadGraph(confNodeProperty).getData();
		//存放首端编码
		List<String> firstCodes = new ArrayList<>();//存放首端编码
		reverseFindFirst(nodeList, firstCodes, alarm.getResourceCode(), alarm.getSegmentId());
		//找出首端
		List<Map<String, Object>> resources = nodeList.stream().filter(e -> firstCodes.contains(e.get("resourceCode"))).collect(Collectors.toList());
		if (CollectionUtils.isEmpty(resources)) {
			log.info("无关联数据");
			return map;
		}
		List<String> stepCodes = resources.stream().map(e -> (String) e.get("resourceCode")).collect(Collectors.toList());
		Integer firstSegmentId = (Integer) resources.get(0).get("segmentId");
		startTime = System.currentTimeMillis();
		// 3、根据首端的服务资源编码集合查询所属业务步骤
		ConfStep step = new ConfStep();
		step.setSystemId(alarm.getSystemId());
		step.setStepCodes(stepCodes);
		List<ConfStep> confSteps = remoteStepService.getStepBySystemIdOrStepCode(step).getData();
		//3.1根据业务找出业务编码
		List<String> businessCodes = confSteps.stream().map(ConfStep::getBusinessCode).collect(Collectors.toList());
		step.setBusinessCodes(businessCodes);

		step.setType(1);//首步骤
		//可能受影响的首步骤
		List<ConfStep> effectStep = remoteStepService.getStepByBusinessCodes(step).getData();
		ConfBusiness confBusiness = new ConfBusiness();

		// 3.2、找出首步骤，回填数据
		List<AlarmReverseAnalysisVo> tempRes = new ArrayList<>();
		for (ConfStep confStep : effectStep) {
			AlarmReverseAnalysisVo vo = new AlarmReverseAnalysisVo();
			vo.setSystemId(alarm.getSystemId());
			vo.setSystemCode(String.valueOf(alarm.getSystemId()));
			vo.setSystemName(alarm.getSystemName());
			String businessCode = confStep.getBusinessCode();
			vo.setBusinessCode(businessCode);
			vo.setBusinessName(remoteBusinessService.getBusinessByBusinessCode(businessCode).getData().getName());
			vo.setManualBusinessCode(confStep.getStepCode());
			vo.setProcessName(confStep.getName());
			vo.setSegmentId(firstSegmentId);
			tempRes.add(vo);
		}


		log.info("告警反推-遍历业务、找出首步骤，回填数据,耗时: {} MS", System.currentTimeMillis() - startTime);
		startTime = System.currentTimeMillis();
		for (AlarmReverseAnalysisVo temp : tempRes) {
			StepStatisticsData ssd = busiAlarmMapper.queryStepStatisticsData(temp, "ads_busi_sys_req_trend_m",
					params.getStatisticsStartTime(), params.getStatisticsEndTime());
			// 判断请求量是否为空，如果为空说明在统计时间范围内，产生这个告警的不可能是该业务步骤，排除之
			if (ssd != null && ssd.getReqCount() != null) {
				temp.setReqCount(ssd.getReqCount());
				temp.setFailRate(ssd.getFailRate());
				temp.setHealthRate(ssd.getHealthRate());
				res.add(temp);
			}
		}

		log.info("告警反推-查询步骤统计表ads_busi_sys_req_trend_m获取统计数据,耗时: {} MS", System.currentTimeMillis() - startTime);
		startTime = System.currentTimeMillis();

		// 4、判断网端类型，查询表id，根据不同类型用relvanceDataId查询web、mid、db表，得到告警网端数据
		Integer tableId = 0;
		tableId = remoteSegmentService.getSegmentTypeNameBySegmentId(alarm.getSegmentId()).getData().getType();
		//todo 这里要注意现在的配置是不是跟以前一样
		SegmentItDataVo segmentData = querySegmentDataById(
				alarm.getRelvanceDataId(), tableId);
		if (segmentData == null) {
			log.info("端数据为空");
			return map;
		}

		log.info("告警反推-用relvanceDataId查询web、mid、db表，得到告警网端数据,耗时: {} MS", System.currentTimeMillis() - startTime);
		startTime = System.currentTimeMillis();

		// 7、判断告警网端专属指标是否有关联指标
		HashMap<String, String> connectEiMap = new LinkedHashMap<>();// todo BpmStartConfig.segmentConnectExcIndicatorsMap.get(segmentData.getResourceCode());
		if (CollectionUtils.isEmpty(connectEiMap)) {
			// 7.1、无关联指标，可能的业务详单概况列表为空
			log.info("无关联指标，可能的业务详单概况列表为空");
			return map;
		}

		log.info("告警反推-判断告警网端专属指标是否有关联指标,耗时: {} MS", System.currentTimeMillis() - startTime);
		startTime = System.currentTimeMillis();

		// 7.2、有关联指标，根据容错时间查询step2表
		List<BusinessStep> bsList = queryStep2(segmentData);

		log.info("告警反推-根据容错时间查询step2表,耗时: {} MS", System.currentTimeMillis() - startTime);
		startTime = System.currentTimeMillis();

		// 8、筛选专属指标关联得上的数据，组装成可能的业务概况vo对象
		bsList.forEach(bs -> {
			if (exclusivesConnect(bs.getExclusiveIndicator(), bs.getResourceCode(),
					segmentData.getExclusiveIndicator(), segmentData.getResourceCode())) {
				PossibleBusinessDetailVo vo = new PossibleBusinessDetailVo();
				vo.setBusinessCode(bs.getBusinessCode());
				vo.setBusinessName(bs.getBusinessName());
				vo.setUserIdentify(bs.getUserIdentify());
				vo.setCustomResCode(bs.getCustomResCode());
				vo.setStartTime(YDateUtil.yyyy_MM_dd_HH_mm_ss(bs.getStartTime()));
				vo.setEndTime(YDateUtil.yyyy_MM_dd_HH_mm_ss(bs.getEndTime()));
				vo.setDealState(bs.getDealState());
				vo.setIsCompletion(bs.getIsCompletion());
				vo.setDealTime(bs.getDealTime());
				vo.setClientFreeTime(bs.getClientFreeTime());
				vo.setAlarmCount(bs.getAlarmCount());
				pbdVoList.add(vo);
			}
		});

		log.info("告警反推-筛选专属指标关联得上的数据，组装成可能的业务概况vo对象,耗时: {} MS", System.currentTimeMillis() - startTime);

		return map;


	}

	/**
	 * 判断专属指标是否关联
	 *
	 * @param exclusiveIndicator1 前端关联指标
	 * @param resourceCode1       前端服务资源编码
	 * @param exclusiveIndicator2 后端关联指标
	 * @param resourceCode2       后端服务资源编码
	 * @return 是否关联
	 */
	private boolean exclusivesConnect(String exclusiveIndicator1, String resourceCode1,
									  String exclusiveIndicator2, String resourceCode2) {
		Map<String, HashMap<String, String>> segmentConnectExcIndicatorsMap = new HashMap<>();
		// todo BpmStartConfig.segmentConnectExcIndicatorsMap;

		if (exclusiveIndicator1 == null || "null".equals(exclusiveIndicator1) || "".equals(exclusiveIndicator1)) {
			return false;
		}
		HashMap<String, String> frontExIs = segmentConnectExcIndicatorsMap.get(resourceCode1);
		HashMap<String, String> backExIs = segmentConnectExcIndicatorsMap.get(resourceCode2);
		if (CollectionUtils.isEmpty(frontExIs) || CollectionUtils.isEmpty(backExIs)) {
			// 1.先从配置判断两个业务资源是否配了关联指标，如果前端没有配或者后端没有配那么就不进行专属指标关联
			return false;
		}
		// 2.前后端资源都配了关联专属指标，但是还不确定关联指标是否已经提上来
		// 判断是否是需要专属指标关联
		boolean needConnect = false;
		// 是否关联成功
		boolean succeedFlag = false;
		String[] frontExcusiveIndicator = exclusiveIndicator1.split("\\|");
		String[] backExcusiveIndicator = exclusiveIndicator2.split("\\|");
		// 遍历前业务资源的指标
		out:
		for (String front : frontExcusiveIndicator) {
			String[] frontKeyValue = front.split("=");
			if (frontExIs.containsKey(frontKeyValue[0]) // 包含了这个指标
					&& (frontExIs.get(frontKeyValue[0]).equals(ISBACKCONNECTFLAG) // 后端关联指标
					|| frontExIs.get(frontKeyValue[0]).equals(ISCONNECTFLAG))) { // 前后端关联指标
				// 3.从具体的数据里找到了前端配了"是后端关联指标"或者"是前后端关联指标"则需要进行与后端专属指标关联
				needConnect = true;
				if (frontKeyValue.length == 1) {
					continue;
				}
				for (String back : backExcusiveIndicator) {
					String[] backKeyValue = back.split("=");
					if (backKeyValue.length == 1) {
						continue;
					}
					if (resourceCode1.equals(resourceCode2)) {
						if (frontKeyValue[1].toLowerCase().equals(backKeyValue[1].toLowerCase())) {
							// 3.1前后端资源相同的无须判断指标类型
							succeedFlag = true;
							break out;
						}
					} else {
						// 4.判断是否是前端关联指标或者是前后端关联指标
						if (backExIs.containsKey(backKeyValue[0])
								&& (backExIs.get(backKeyValue[0]).equals(ISFRONTCONNECTFLAG)
								|| backExIs.get(backKeyValue[0]).equals(ISCONNECTFLAG))) {
							if (frontKeyValue[1].toLowerCase().equals(backKeyValue[1].toLowerCase())) {
								// 5.找到后端与前端相同的指标和值，认为关联成功
								succeedFlag = true;
								break out;
							}
						}
					}
				}
			}
		}

		// 不需要进行关联
		if (!needConnect) {
			return false;
		}
		// 需要关联
		if (needConnect && succeedFlag) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * 根据容错时间查询step2表
	 *
	 * @param segmentData 告警网端数据
	 * @return 步骤集合
	 * @throws Exception
	 */
	private List<BusinessStep> queryStep2(SegmentItDataVo segmentData) throws Exception {

		// 计算容错时间范围
//        String containTimeStr = sysConfigService.selectConfigByKey("business.detail.e2e.containTime");
		String containTimeStr = ParamResolver.getStr(ConfigKey.E2E_CONTAIN_TIME);
		log.info("[告警反推分析]容错时间：" + containTimeStr + "ms");
		Long containTimeUsec = org.springframework.util.StringUtils.isEmpty(containTimeStr) ? 0 : Long.parseLong(containTimeStr) * 1000L;
		Long serverResponseStartTimeUsecWrapper = segmentData.getServerResponseStartTimeUsec() - containTimeUsec;
		Long serverResponseEndTimeUsecWrapper = segmentData.getServerResponseEndTimeUsec() + containTimeUsec;

		List<BusinessStep> bsList = businessDetailMapper.queryBusinessStepByParam(serverResponseStartTimeUsecWrapper, serverResponseEndTimeUsecWrapper, segmentData.getSystemId());
		return bsList;
	}


	/**
	 * 根据不同类型用relvanceDataId查询web、mid、db表
	 *
	 * @param relvanceDataId id
	 * @param tableId        查询的表类型
	 * @return 网端数据对象
	 */
	private SegmentItDataVo querySegmentDataById(String relvanceDataId, Integer tableId) throws Exception {
		// 1、获取需要查询的表名
		String preTableName = null;
		if (tableId == 0) {
			preTableName = "dwd_business_web";
		} else if (tableId == 1) {
			preTableName = "dwd_business_mid";
		} else if (tableId == 2) {
			preTableName = "dwd_business_db";
		}
		// 3、处理结果
		SegmentItDataVo vo = businessDetailMapper.querySegmentItDataById(relvanceDataId, preTableName);
		return vo;
	}

	/**
	 * 往前推寻找首端
	 *
	 * @param ssrList         所有端结构数据
	 * @param firstCodes      首端资源编码（步骤编码）
	 * @param oriResourceCode 资源编码
	 * @param oriSegmentId    网端id
	 */
	private boolean reverseFindFirst(List<Map<String, Object>> ssrList, List<String> firstCodes, String oriResourceCode, Integer oriSegmentId) {
		for (Map<String, Object> map : ssrList) {
			Integer segmentId = (Integer) map.get("segmentId");
			String resourceCode = (String) map.get("resourceCode");
			if (oriSegmentId.equals(segmentId) && oriResourceCode.equals(resourceCode)) {
				firstCodes.add(resourceCode);
				return true;
			}
			List<Map<String, Object>> childNodes = (List<Map<String, Object>>) map.get("childNodes");
			if (ObjectUtil.isNotEmpty(childNodes)) {
				if (reverseFindFirst(childNodes, firstCodes, oriResourceCode, oriSegmentId)) {
					firstCodes.add(resourceCode);
				}
			}
		}
		return false;
	}


	/**
	 * 根据告警数据id查询告警信息(非首端)
	 *
	 * @param params 告警反推分析请求参数
	 * @return 告警信息对象
	 */
	private BasalAlarm queryAlarmDataById(AlarmReverseAnalysisParams params) throws Exception {

		BasalAlarm alarm = businessDetailMapper.queryBaseAlarmInfoById(params.getId());
		return alarm;
	}

	/**
	 * 根据id查询告警详情
	 *
	 * @param id
	 * @param startTime
	 * @param endTime
	 * @return
	 */
	@Override
	public BaseBusinessAlarmVo getBusinessAlarmDetail(String id, String startTime, String endTime) {
		BaseBusinessAlarmVo detail = new BaseBusinessAlarmVo();
		//查询数据库
		Map<String, Object> map = baseAlarmMapper.selectBaseAlarmDetail(id);
		//处理结果
		if (map != null && map.size() > 0) {
			//ID
			detail.setId(id);
			//触发告警对象
			detail.setAlarmObject(DataTypeUtil.objectToString(map.get("alarmObject")));
			//步骤名称
			detail.setStepName(DataTypeUtil.objectToString(map.get("stepName")));
			//业务名称
			detail.setBusinessName(DataTypeUtil.objectToString(map.get("businessName")));
			//告警网端
			detail.setSegmentName(DataTypeUtil.objectToString(map.get("segmentName")));
			//系统名称
			detail.setSystemName(DataTypeUtil.objectToString(map.get("systemName")));
			//告警级别
			detail.setAlarmLevelName(DataTypeUtil.objectToString(map.get("alarmLevelName")));
			//告警指标（前端用告警类型）
			detail.setAlarmIndicatorName(DataTypeUtil.objectToString(map.get("alarmIndicatorName")));
			//告警时间
			detail.setAlarmTriggerTime(DataTypeUtil.objectToString(map.get("alarmTriggerTime")));
			//请求ip
			detail.setRequestIp(DataTypeUtil.objectToString(map.get("requestIp")));
			//响应ip
			detail.setResponseIp(DataTypeUtil.objectToString(map.get("responseIp")));
			//响应端口
			detail.setResponsePort(DataTypeUtil.objectToInteger(map.get("responsePort")));
			//专属指标
			detail.setExclusiveIndicator(DataTypeUtil.objectToString(map.get("exclusiveIndicator")));
			//告警现象
			detail.setAlarmContext(DataTypeUtil.objectToString(map.get("alarmContext")));
			//告警对象类型
			detail.setObjectTypeName(DataTypeUtil.objectToString(map.get("objectTypeName")));
			//查询业务和步骤用的id
			detail.setBusinessStepId(DataTypeUtil.objectToString(map.get("relvanceStepId")));
			//请求端口
			detail.setRequestPort(DataTypeUtil.objectToInteger(map.get("requestPort")));
			//探针IP:端口
			detail.setProbeIp(DataTypeUtil.objectToString(map.get("probeIp")));
			//告警开始时间,精确到豪秒
			detail.setStartTimeSec(DataTypeUtil.objectToLong(map.get("startTimeSec")));
			//告警结束时间,精确到豪秒
			detail.setEndTimeSec(DataTypeUtil.objectToLong(map.get("endTimeSec")));
		}
		return detail;
	}

	/**
	 * 根据步骤id查询business_step
	 *
	 * @param id
	 * @param startTime
	 * @param endTime
	 * @return
	 */
	@Override
	public List<AlarmBusinessStepVo> getStepDetailList(String id, String startTime, String endTime) {
		List<AlarmBusinessStepVo> list = new ArrayList<>();

		//查询数据库
		Map<String, Object> stepMap = stepDetailMapper.selectStepDetail(Long.valueOf(id), null);
		if (stepMap != null && stepMap.size() > 0) {
			AlarmBusinessStepVo vo = new AlarmBusinessStepVo();
			BeanUtil.copyProperties(stepMap, vo);
			//ID
			vo.setIdStr(DataTypeUtil.objectToString(stepMap.get("id")));
			//开始时间时间戳
			LocalDateTime localStartTime = (LocalDateTime) stepMap.get("startTime");
			String startTimeStr = YDateUtil.yyyy_MM_dd_HH_mm_ss(localStartTime);
			vo.setStartTime(startTimeStr);
			vo.setStartTimeUsec(YDateUtil.getUsecFromDateStr(startTimeStr) / 1000000);
			//结束时间
			String endTimeStr = YDateUtil.yyyy_MM_dd_HH_mm_ss((LocalDateTime) stepMap.get("endTime"));
			vo.setEndTime(endTimeStr);
			//开始时间时间戳
			vo.setEndTimeUsec(YDateUtil.getUsecFromDateStr(endTimeStr) / 1000000);
			list.add(vo);
		}
		return list;
	}


	/**
	 * 根据步骤id查询business_detail
	 *
	 * @param id
	 * @param startTime
	 * @param endTime
	 * @return
	 */
	@Override
	public R getBusinessDetailList(String id, String startTime, String endTime) {
		List<BusinessDetail> list = new ArrayList<>();

		//根据步骤ID查询详单ID
		String detailId = stepDetailMapper.selectStepDetailId(id);
		if (StringUtils.isNotEmpty(detailId)) {
			//根据详单ID查询详单明细
			Map<String, Object> map = businessDetailMapper.selectBusinessDetail(Long.valueOf(detailId));
			if (map != null && map.size() > 0) {
				BusinessDetail vo = new BusinessDetail();
				vo.setBusinessCode(DataTypeUtil.objectToString(map.get("businessCode")));
				vo.setBusinessName(DataTypeUtil.objectToString(map.get("businessName")));
				vo.setDealState(DataTypeUtil.objectToInteger(map.get("dealState")));
				vo.setOperatingSystem(DataTypeUtil.objectToString(map.get("operatingSystem")));
				vo.setBrowser(DataTypeUtil.objectToString(map.get("browser")));
				vo.setDealTime(DataTypeUtil.objectToLong(map.get("dealTime")));
				vo.setClientFreeTime(DataTypeUtil.objectToLong(map.get("clientFreeTime")));
				vo.setClientTranslateTime(DataTypeUtil.objectToLong(map.get("clientTranslateTime")));
				vo.setServerResponseTime(DataTypeUtil.objectToLong(map.get("serverResponseTime")));
				vo.setServerTranslateTime(DataTypeUtil.objectToLong(map.get("serverTranslateTime")));
				vo.setStartTime(DataTypeUtil.objectToString(map.get("startTime")).replace("T", " "));
				vo.setEndTime(DataTypeUtil.objectToString(map.get("endTime")).replace("T", " "));
				list.add(vo);
			}
		}
//		}
		return R.ok(list);
	}


	/**
	 * 不需要展示的字段
	 *
	 * @param businessAlarmVoFields
	 * @param displayFields
	 * @return
	 */
	private List<String> getExcludeColumnFields(HashMap<String, String> businessAlarmVoFields, List<String> displayFields) {
		List<String> excludeColumnFields = new ArrayList<>();
		for (Map.Entry<String, String> stringStringEntry : businessAlarmVoFields.entrySet()) {
			if (!displayFields.contains(stringStringEntry.getKey())) {
				excludeColumnFields.add(stringStringEntry.getValue());
			}
		}
		return excludeColumnFields;
	}


	/**
	 * 从详单查询那边跳转过来，有可能开始时间和结束时间不在同一天，
	 * 数据可能落在结束时间的后一天的表
	 *
	 * @param tableName
	 * @param params
	 * @param tablesList
	 */
	private void isSameDay(String tableName, AlarmParams params, List<String> tablesList) {
		if (params.getRelvanceDataId() != null && !("").equals(params.getRelvanceDataId())) {
			SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			Date startTime = new Date();
			Date endTime = new Date();
			try {
				startTime = simpleDateFormat.parse(params.getStartTime());
				endTime = simpleDateFormat.parse(params.getEndTime());
			} catch (ParseException e) {
				e.printStackTrace();
			}
			if (!YDateUtil.isSameDay(startTime, endTime)) {
				simpleDateFormat = new SimpleDateFormat("yyyyMMdd");

				String endTimeStr = simpleDateFormat.format(endTime);
				tablesList.add(tableName + "_" + endTimeStr);
			}
		}
	}

	public static String getData(long data) {
		if (data > 1024 * 1024) {
			BigDecimal b = new BigDecimal((double) data / (1024 * 1024));
			return b.setScale(2, BigDecimal.ROUND_HALF_UP) + "MB";

		} else if (data > 1024) {
			BigDecimal b = new BigDecimal((double) data / 1024);
			return b.setScale(2, BigDecimal.ROUND_HALF_UP) + "KB";

		} else {
			return data + "B";
		}
	}

}
